use std::{fs::File, io::Write, path::PathBuf};

use chrono::{DateTime, Datelike, Local};
use quic_secs_driver::enums::LogLevel;

#[cfg(unix)]
pub const NEWLINE: &'static str = "\n";
#[cfg(windows)]
pub const NEWLINE: &'static str = "\r\n";

#[derive(Clone)]
pub(crate) struct Logger {
    sender: tokio::sync::mpsc::Sender<LogEntity>,
    pub is_write_secs1: bool,
}

impl Logger {
    pub(crate) fn new(
        dir: PathBuf,
        name: String,
        file_max_size: u64,
        is_write_secs1: bool,
    ) -> Self {
        let (tx, rx) = tokio::sync::mpsc::channel::<LogEntity>(20);
        let provider = LoggerProivder {
            dir,
            name,
            file_max_size,
            is_write_secs1,
            file_current_size: 00,
            file_index: 0,
            file_last_day: 0,
            file: None,
            file_secs1: None,
        };
        tokio::spawn(start_logger_receiver(provider, rx));
        Self {
            sender: tx,
            is_write_secs1,
        }
    }

    // pub(crate) fn info(&self, time: String, message: String) {
    //     _ = crate::block_on(self.sender.send(LogEntity {
    //         time,
    //         level: LogLevel::Info,
    //         message,
    //     }));
    // }

    pub(crate) async fn info_async(&self, time: String, message: String) {
        _ = self
            .sender
            .send(LogEntity {
                time,
                level: LogLevel::Info,
                message,
                secs1: None,
            })
            .await;
    }

    pub(crate) async fn info_secs1_async(
        &self,
        time: String,
        message: String,
        secs1: Option<String>,
    ) {
        _ = self
            .sender
            .send(LogEntity {
                time,
                level: LogLevel::Info,
                message,
                secs1,
            })
            .await;
    }

    // pub(crate) fn log(&self, time: String, level: LogLevel, message: String) {
    //     _ = crate::block_on(self.sender.send(LogEntity {
    //         time,
    //         level,
    //         message,
    //     }));
    // }

    pub(crate) async fn log_async(&self, time: String, level: LogLevel, message: String) {
        _ = self
            .sender
            .send(LogEntity {
                time,
                level,
                message,
                secs1: None,
            })
            .await;
    }
}

struct LoggerProivder {
    dir: PathBuf,
    name: String,
    is_write_secs1: bool,
    file_max_size: u64,
    file_current_size: u64,
    file_index: usize,
    file_last_day: u32,
    file: Option<File>,
    file_secs1: Option<File>,
}

impl LoggerProivder {
    pub(crate) fn write(&mut self, entity: LogEntity) {
        let current = chrono::Local::now();
        if self.file.is_none()
            || self.file_current_size >= self.file_max_size
            || current.day() != self.file_last_day
        {
            self.reset(current);
        }
        if let Some(file) = self.file.as_mut() {
            let content = format!(
                "{} {:?} {}{NEWLINE}",
                entity.time, entity.level, entity.message
            );
            let bytes = content.as_bytes();
            _ = file.write_all(bytes);
            self.file_current_size += bytes.len() as u64;
            _ = file.flush()
        }
        if self.is_write_secs1 {
            if let Some(sec1_file_inner) = self.file_secs1.as_mut() {
                if let Some(str_secs1) = entity.secs1 {
                    let content = format!("{} {}{NEWLINE}", entity.time, str_secs1);
                    let bytes = content.as_bytes();
                    _ = sec1_file_inner.write_all(bytes);
                    _ = sec1_file_inner.flush()
                }
            }
        }
    }

    fn reset(&mut self, current_time: DateTime<Local>) {
        if let Some(mut file) = self.file.take() {
            _ = file.flush();
        }
        if !self.dir.exists() {
            if let Err(_) = std::fs::create_dir_all(&self.dir) {
                return;
            }
        }
        let (file_size, path) = loop {
            self.file_index += 1;
            let path = self.dir.join(format!(
                "{}.{}.{:06}.log",
                self.name,
                current_time.format("%Y-%m-%d"),
                self.file_index
            ));
            if !path.exists() {
                break (0, path);
            }
            if path.is_file() {
                if let Ok(mt) = path.metadata() {
                    let file_size = mt.len();
                    if file_size < self.file_max_size {
                        break (file_size, path);
                    }
                }
            }
        };
        if let Some(mut o_file) = self.file.take() {
            _ = o_file.flush();
        }
        if let Ok(file) = std::fs::OpenOptions::new()
            .create(true)
            .write(true)
            .append(true)
            .open(&path)
        {
            self.file_last_day = current_time.day();
            self.file_current_size = file_size;
            self.file.replace(file);
        }
        if self.is_write_secs1 {
            if let Some(mut o_file) = self.file_secs1.take() {
                _ = o_file.flush();
            }
            let path = self.dir.join(format!(
                "{}.{}.{:06}.secs1.log",
                self.name,
                current_time.format("%Y-%m-%d"),
                self.file_index
            ));
            if let Ok(file) = std::fs::OpenOptions::new()
                .create(true)
                .write(true)
                .append(true)
                .open(&path)
            {
                self.file_secs1.replace(file);
            }
        }
    }
}

async fn start_logger_receiver(
    mut provider: LoggerProivder,
    mut rx: tokio::sync::mpsc::Receiver<LogEntity>,
) {
    loop {
        match rx.recv().await {
            Some(entity) => {
                provider.write(entity);
            }
            None => {
                break;
            }
        }
    }
}

impl Drop for LoggerProivder {
    fn drop(&mut self) {
        if let Some(mut file) = self.file.take() {
            _ = file.flush();
        }
    }
}

pub(crate) struct LogEntity {
    time: String,
    level: LogLevel,
    message: String,
    secs1: Option<String>,
}
